/**
 * @file select
 * @author liaoxuezhi@baidu.com
 *
 * https://github.com/JedWatson/react-select
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import ReactSelect from 'react-select';
import * as assign from 'object-assign';
import * as cx from 'classnames';
import {findByAttrValue} from '../../../util/helper';
import {Enhance as FormItem} from '../form-item';
import {render, fetch} from '../../renderer';
import {confirm, alert} from '../../../widgets/alert/alert';

// @require 'react-select/dist/react-select.css';
// jest 不能支持这种写法
// import 'react-select/dist/react-select.css';


@FormItem({
  type: 'chained-select',
  haveFetchError: false,
  haveFetch: false,
  validateSchema: 'http://amis.baidu.com/schemas/form.json#/definitions/formItem'
})
export default class ChainedSelect extends PureComponent {
  static propTypes = {
    className: PropTypes.string,
    formMode: PropTypes.string,
    formHorizontal: PropTypes.object,
    joinValues: PropTypes.bool.isRequired,
    delimiter: PropTypes.string.isRequired
  };

  static defaultProps = {
    value: '',
    placeholder: '请选择',
    joinValues: true,
    delimiter: ',',
    addButtonClassName: 'm-l-xs btn-default btn-sm rounded v-top',
    maxLevel: 0, // 不限制
    additionalControls: []
  };

  constructor(props) {
    super(props);

    this.state = {
      chained: props.options ? [props.options] : []
    };

    this.changeValue = this.changeValue.bind(this);
    this.close = this.close.bind(this);
    this.onDialogResolved = this.onDialogResolved.bind(this);
    this.optionRenderer = this.optionRenderer.bind(this);
    this.triggerFetchOptions = this.triggerFetchOptions.bind(this);
  }

  componentDidMount() {
    const {
      source,
      fetchSource,
      data
    } = this.props;
    const chained = this.state.chained;

    if (!chained.length && source) {
      fetchSource(assign({}, data, {
        value: this.props.getValue(),
        level: 0
      }));
    }
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.sourceFetching && !nextProps.sourceFetching && nextProps.sourceData) {
      const sourceData = nextProps.sourceData;
      const chained = this.state.chained.concat();
      const options = sourceData && sourceData.hasOwnProperty('options') ? sourceData.options : sourceData;

      chained.push(options);
      this.setState({
        chained
      }, this.triggerFetchOptions);
    }

    if (props.options !== nextProps.options) {
      this.setState({
        chained: nextProps.options ? [nextProps.options] : []
      })
    }
  }

  triggerFetchOptions() {
    const {
      chained
    } = this.state;

    const {
      joinValues,
      delimiter,
      source,
      fetchSource,
      data,
      maxLevel
    } = this.props;
    let value = this.props.formItemValue;

    const level = chained.length;

    if (joinValues) {
      value = value ? String(value).split(delimiter) : [];
    } else {
      value = value || [];
    }
    const length = value.length;

    (!maxLevel || level < maxLevel - 1) && level <= length && source && fetchSource(assign({}, data, {
      value,
      level: level + 1,
      parent: value[level],
      parentId: value[level] && value[level].value || value[level]
    }));

  }

  changeValue(newValue, level) {
    const {
      joinValues,
      delimiter,
      source,
      fetchSource,
      data,
      maxLevel
    } = this.props;
    const chained = this.state.chained.concat();
    let value = this.props.getValue();
    let values;

    if (joinValues) {
      value = values = value ? String(value).split(delimiter) : [];
      value.splice(level, value.length - level);
      newValue = newValue && newValue.value || newValue;
      newValue && value.push(newValue);
      value = value.join(delimiter);
    } else {
      value = values = value && value.concat() || [];
      value.splice(level, value.length - level);
      value.push(newValue);
    }

    this.props.setValue(value);
    chained.splice(level + 1, chained.length - level);
    this.setState({
      chained
    });

    let ctx = assign({}, data, {
      value,
      level: level + 1,
      parent: values[level],
      parentId: values[level] && values[level].value || values[level]
    });

    (!maxLevel || level < maxLevel - 1) && source && ctx.parentId &&  fetchSource(ctx);
  }

  addOption(level) {
    const {
      getValue,
      delimiter,
      addApi,
      joinValues,
      additionalControls
    } = this.props;
    const value = getValue();
    let values = value || [];

    if (value && typeof value === 'string' && joinValues) {
      values = value.split(delimiter);
    }

    this.setState({
      dialogOpen: true,
      dialogSchema: {
        title: '添加',
        body: {
          type: 'form',
          api: addApi,
          controls: [
            {
              type: 'hidden',
              name: 'id'
            },
            {
              type: 'text',
              name: 'label',
              label: '名称'
            }
          ].concat(additionalControls)
        }
      },
      dialogData: {
        level: level,
        parent: values[level - 1],
        parentId: values[level - 1] && values[level - 1].value || values[level - 1]
      }
    })
  }

  deleteOption(level, option) {
    const {
      getValue,
      delimiter,
      editApi,
      joinValues
    } = this.props;
    const value = getValue();
    let values = value || [];

    if (value && typeof value === 'string' && joinValues) {
      values = value.split(delimiter);
    }

    this.setState({
      dialogData: {
        id: option.value,
        value: option.value,
        label: option.label,
        parent: values[level - 1],
        parentId: values[level - 1] && values[level - 1].value || values[level - 1],
        level
      }
    });

    const {
      deleteApi
    } = this.props;
    confirm('确定要删除指定项？')
      .then(() => fetch(deleteApi, {
        id: option.value,
        value: option.value,
        label: option.label,
        parent: values[level - 1],
        parentId: values[level - 1] && values[level - 1].value || values[level - 1],
        level
      }))
      .then(this.onDialogResolved)
      .catch(e => alert(e.message || e))
  }

  editOption(level, option) {
    const {
      getValue,
      delimiter,
      editApi,
      joinValues,
      additionalControls
    } = this.props;
    const value = getValue();
    let values = value || [];

    if (value && typeof value === 'string' && joinValues) {
      values = value.split(delimiter);
    }

    this.setState({
      dialogOpen: true,
      dialogSchema: {
        title: '编辑',
        body: {
          type: 'form',
          api: editApi,
          controls: [
            {
              type: 'hidden',
              name: 'id'
            },
            {
              type: 'hidden',
              name: 'value'
            },
            {
              type: 'text',
              name: 'label',
              label: '名称'
            }
          ].concat(additionalControls)
        }
      },
      dialogData: assign({}, option, {
        level: level,
        id: option.value,
        parent: values[level - 1],
        parentId: values[level - 1] && values[level - 1].value || values[level - 1]
      })
    })
  }

  onDialogResolved() {
    const {
      fetchSource,
      source,
      getValue
    } = this.props;
    const data = this.state.dialogData;
    const value = getValue();
    const chained = this.state.chained.concat();
    const level = data.level;

    chained.splice(level, chained.length - level);
    this.setState({
      chained
    });
    this.close();
    source && fetchSource(assign({}, data, {
      value,
      level: level
    }));
  }

  close() {
    this.setState({
      dialogOpen: false
    })
  }

  optionRenderer(level, option) {
    const {
      editApi,
      deleteApi
    } = this.props;

    return (
      <div>

        {deleteApi ? (
            <span onMouseDown={e => {
              e.stopPropagation();
              e.preventDefault();
              this.deleteOption(level, option)
            }} className="pull-right padder-xs m-l-sm m-r-n-sm"><i className="fa  fa-times"/></span>
          ) : null}

        {editApi ? (
            <span onMouseDown={e => {
              e.stopPropagation();
              e.preventDefault();
              this.editOption(level, option)
            }} className="pull-right padder-xs m-l-sm m-r-n-sm"><i className="fa fa-pencil"/></span>
          ) : null}

        {option.label}
      </div>
    )
  }

  renderInput() {
    const {
      joinValues,
      delimiter,
      disabled,
      sourceFetching,
      placeholder,
      addApi,
      editApi,
      deleteApi,
      addButtonClassName,
      locals,
      $path
    } = this.props;
    const chained = this.state.chained;
    const value = this.props.getValue();
    let values = value || [];

    if (value && typeof value === 'string' && joinValues) {
      values = value.split(delimiter);
    } else if (typeof value === 'number') {
      values = [String(value)];
    }

    const {
      dialogOpen,
      dialogSchema,
      dialogData
    } = this.state;
    const optionRenderer = editApi || deleteApi ? this.optionRenderer : null;

    return (
      <div key="input" className="chained-select">
        {chained.map((options, key) => (
          <span
            key={key}
          >
            <ReactSelect
              disabled={disabled || sourceFetching}
              autosize={false}
              clearable={true}
              options={options}
              optionRenderer={optionRenderer ? optionRenderer.bind(this, key) : null}
              placeholder={placeholder}
              value={findByAttrValue(options, 'value', values[key] && values[key].value || values[key])}
              onChange={newValue => this.changeValue(newValue, key)}
            />

            {addApi ? (
              <button
                type="button"
                onClick={this.addOption.bind(this, key)}
                className={cx('btn', addButtonClassName)}
                disabled={disabled}
              >
                <i className="fa fa-plus" />
              </button>
              ) : null}
          </span>
        ))}

        {sourceFetching && (<i key="loading" className="fa fa-spinner fa-spin fa-2x fa-fw v-top" />)}
        {dialogOpen ?
          render(`${$path}/dialog`, dialogSchema, {
            data: dialogData,
            locals,
            onClose: this.close,
            onResolved: this.onDialogResolved
          })
          : null}
      </div>
    );
  }

  render() {

    let input = this.renderInput();

    return (
      input
    );
  }
}
